package mwt.xml.xdbforms.xforms.impl;

import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import mwt.xml.xdbforms.dbms.exceptions.TypeNotFoundException;
import mwt.xml.xdbforms.dbms.metadata.impl.ColumnMetadata;
import mwt.xml.xdbforms.dbms.metadata.impl.ForeignKeyMetadata;
import mwt.xml.xdbforms.dbms.metadata.impl.PrimaryKeyMetadata;
import mwt.xml.xdbforms.dbms.metadata.impl.TableMetadata;
import mwt.xml.xdbforms.xforms.TypeHandler;
import mwt.xml.xdbforms.xforms.XFormsModelManager;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;

public class XFormsModelManagerImpl implements XFormsModelManager {

    private Document document;

    public XFormsModelManagerImpl() throws ParserConfigurationException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        dbf.setValidating(false);

        DocumentBuilder db = dbf.newDocumentBuilder();

        this.document = db.newDocument();
    }

    public Document getModelAsDocument(TableMetadata table, String action) throws TypeNotFoundException {
        this.buildDocument(table, action);
        
        return this.document;
    }

    public String getModelAsString(TableMetadata table, String action) throws TypeNotFoundException {
        this.buildDocument(table, action);

        return printDocument();
    }
    
    public String getModelAsString(TableMetadata table, String action, Map<String,String> row) throws TypeNotFoundException {
        this.buildDocument(table, action, row);

        return printDocument();
    }

    private void buildDocument(TableMetadata table, String action) throws TypeNotFoundException {
        TypeHandler typeHandler = new MySQLTypeHandler();

        Element model = this.document.createElement("xf:model");
        Element instance = this.document.createElement("xf:instance");
        Element data = this.document.createElement("data");
        data.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "");
        
        Element schemaName = this.document.createElement("schema-name");
        schemaName.setAttribute("xsi:type", "xsd:string");
        schemaName.setTextContent(table.getSchemaName());
        data.appendChild(schemaName);

        Element tableName = this.document.createElement("table-name");
        tableName.setAttribute("xsi:type", "xsd:string");
        tableName.setTextContent(table.getName());
        data.appendChild(tableName);

        // primary key
        if (table.getPks().size() > 0) {
            Element primaryKey = this.document.createElement("primary-key");
            primaryKey.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "");

            // xf:data elements primary key
            for (PrimaryKeyMetadata pkm : table.getPks()) {
                Element xcol = this.document.createElement("col-name");
                xcol.setAttribute("xsi:type", "xsd:string");
                xcol.setTextContent(pkm.getColumnName());

                primaryKey.appendChild(xcol);
            }
            data.appendChild(primaryKey);
        }

        // foreign key
        if (table.getImportedKeys().size() > 0) {
            Element foreignKey = this.document.createElement("foreign-key");
            foreignKey.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "");

            // xf:data elements foreign key
            for (ForeignKeyMetadata fkm : table.getImportedKeys()) {
                Element xcol = this.document.createElement("col-name");
                xcol.setAttribute("xsi:type", "xsd:string");
                xcol.setTextContent(fkm.getFkColumnName());

                foreignKey.appendChild(xcol);
            }
            data.appendChild(foreignKey);
        }

        // xf::data elements
        for (ColumnMetadata col : table.getColumns()) {
            Element xcol = this.document.createElement(col.getName());
            xcol.setAttribute("xsi:type", typeHandler.getSchemaTypeName(col.getTypeName()));

            if (col.getDefVal() != null) {
                xcol.setTextContent(col.getDefVal());
            }
            if ("YES".equals(col.getIsAutoincrement())) {
                xcol.setAttribute("autoincr", "true");
            } else {
                xcol.setAttribute("autoincr", "false");
            }
            data.appendChild(xcol);
        }

        instance.appendChild(data);
        model.appendChild(instance);

        // xf:bind elements
        for (ColumnMetadata col : table.getColumns()) {
            if ("NO".equals(col.getIsAutoincrement())) {
                Element bind = this.document.createElement("xf:bind");
                bind.setAttribute("id", col.getName());
                bind.setAttribute("nodeset", "/data/"+col.getName());

                bind.setAttribute("xsi:type", typeHandler.getSchemaTypeName(col.getTypeName()));

                if ("NO".equals(col.getIsNullable())) {
                   bind.setAttribute("required", "true()");
                } else {
                   bind.setAttribute("required", "false()");
                }

                model.appendChild(bind);
            }
        }

        // xf:submission elements
        Element submission = this.document.createElement("xf:submission");
        submission.setAttribute("id", table.getName());
        submission.setAttribute("method", "post");
        submission.setAttribute("action", action);
        submission.setAttribute("separator", "&");

        model.appendChild(submission);
        this.document.appendChild(model);
    }

   private void buildDocument(TableMetadata table, String action, Map<String,String> row) throws TypeNotFoundException {
        TypeHandler typeHandler = new MySQLTypeHandler();

        Element model = this.document.createElement("xf:model");
        Element instance = this.document.createElement("xf:instance");
        Element data = this.document.createElement("data");
        data.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "");

        Element schemaName = this.document.createElement("schema-name");
        schemaName.setAttribute("xsi:type", "xsd:string");
        schemaName.setTextContent(table.getSchemaName());
        data.appendChild(schemaName);

        Element tableName = this.document.createElement("table-name");
        tableName.setAttribute("xsi:type", "xsd:string");
        tableName.setTextContent(table.getName());
        data.appendChild(tableName);

        // primary key
        if (table.getPks().size() > 0) {
            Element primaryKey = this.document.createElement("primary-key");
            primaryKey.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "");

            // xf:data elements primary key
            for (PrimaryKeyMetadata pkm : table.getPks()) {
                Element xcol = this.document.createElement("col-name");
                xcol.setAttribute("xsi:type", "xsd:string");
                xcol.setTextContent(pkm.getColumnName());

                primaryKey.appendChild(xcol);
            }
            data.appendChild(primaryKey);
        }

        // foreign key
        if (table.getImportedKeys().size() > 0) {
            Element foreignKey = this.document.createElement("foreign-key");
            foreignKey.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns", "");

            // xf:data elements foreign key
            for (ForeignKeyMetadata fkm : table.getImportedKeys()) {
                Element xcol = this.document.createElement("col-name");
                xcol.setAttribute("xsi:type", "xsd:string");
                xcol.setTextContent(fkm.getFkColumnName());

                foreignKey.appendChild(xcol);
            }
            data.appendChild(foreignKey);
        }

        // xf::data elements
        for (ColumnMetadata col : table.getColumns()) {
            Element xcol = this.document.createElement(col.getName());
            xcol.setAttribute("xsi:type", typeHandler.getSchemaTypeName(col.getTypeName()));

            xcol.setTextContent(row.get(col.getName()));
            if ("YES".equals(col.getIsAutoincrement())) {
                xcol.setAttribute("autoincr", "true");
            } else {
                xcol.setAttribute("autoincr", "false");
            }
            
            data.appendChild(xcol);
        }

        instance.appendChild(data);
        model.appendChild(instance);

        // xf:bind elements
        for (ColumnMetadata col : table.getColumns()) {
            if ("NO".equals(col.getIsAutoincrement())) {
                Element bind = this.document.createElement("xf:bind");
                bind.setAttribute("id", col.getName());
                bind.setAttribute("nodeset", "/data/"+col.getName());

                bind.setAttribute("xsi:type", typeHandler.getSchemaTypeName(col.getTypeName()));

                if ("NO".equals(col.getIsNullable())) {
                   bind.setAttribute("required", "true()");
                } else {
                   bind.setAttribute("required", "false()");
                }

                model.appendChild(bind);
            }
        }

        // xf:submission elements
        Element submission = this.document.createElement("xf:submission");
        submission.setAttribute("id", table.getName());
        submission.setAttribute("method", "post");
        submission.setAttribute("action", action);
        submission.setAttribute("separator", "&");

        model.appendChild(submission);
        this.document.appendChild(model);
    }

    private String printDocument() {
        DOMImplementationLS ls = (DOMImplementationLS) this.document.getImplementation();
        LSOutput lso = ls.createLSOutput();
        LSSerializer lss = ls.createLSSerializer();

        lso.setEncoding("ISO-8859-1");
        lss.getDomConfig().setParameter("canonical-form", false);
        lss.getDomConfig().setParameter("format-pretty-print", true);
        lss.getDomConfig().setParameter("xml-declaration", false);

        return lss.writeToString(document);
    }
}